/*
 * Copyright (c) 2025 Huawei Device, Inc. Ltd. and <马弓手>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "cJSON.h"
#include "ConnPool.h"
#include "HotCodePushPlugin.h"
#include "Application.h"
#include "CordovaViewController.h"
#include "FileCache.h"
#include <bundle/native_interface_bundle.h>

REGISTER_PLUGIN_CLASS(HotCodePushPlugin)
bool HotCodePushPlugin::execute(const string&  action, cJSON* args, CallbackContext cbc)
{
    if(action == "jsInitPlugin") {
        cJSON* json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "action", "chcp_nothingToUpdate");
        cJSON_AddNumberToObject(json, "error", 2);
        cJSON_AddNullToObject(json, "data");
        cbc.success(json);
    }
    
    if(action == "jsFetchUpdate") {
        cJSON* pJson = cJSON_GetArrayItem(args, 0);
        m_fetchUpdateOptions.InitFetchUpdateOptions(pJson);
        if(!m_fetchUpdateOptions.getConfigUrl().empty()) {
            m_strChcpConfigUrl = m_fetchUpdateOptions.getConfigUrl();
        }
        m_cbc = cbc;
        m_checkUpdatePlugin.Start(this);
    }
    
    if(action == "jsInstallUpdate" || action == "jsRequestAppUpdate" || action == "jsIsUpdateAvailableForInstallation") {
        if(m_nUpdateFlag == 1) {
            OH_NativeBundle_ApplicationInfo application = OH_NativeBundle_GetCurrentApplicationInfo();
            string strBundleName = application.bundleName;
            
            return executeArkTs("/pages/ReStart", 0, strBundleName.c_str(), "HotCodePush", cbc);
        }
            
        cJSON* json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "action", "chcp_nothingToUpdate");
        cJSON_AddNumberToObject(json, "error", 2);
        cJSON_AddNullToObject(json, "data");
        cbc.success(json);
    }
    
    if(action == "jsConfigure") {
        cJSON* json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "action", "chcp_nothingToUpdate");
        cJSON_AddNumberToObject(json, "error", 2);
        cJSON_AddNullToObject(json, "data");
        cbc.success(json);
    }
    
    if(action == "jsGetVersionInfo") {
        cJSON* json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "action", "chcp_nothingToUpdate");
        cJSON_AddNumberToObject(json, "error", 2);
        cJSON_AddNullToObject(json, "data");
        cbc.success(json);
    }
    
    return true;
}

void HotCodePushPlugin::pluginInitialize()
{
    m_pBuf = malloc(const_max_buf);
    m_pChcpFile = NULL;
    m_pCacheChcpFile = NULL;
    m_pChcpManifestFile = NULL;
    m_pChcpChacheManifestFile = NULL;
    Application::g_strHotCodeUpdateDirectory = Application::g_databaseDir.substr(0, Application::g_databaseDir.find("database")-1)+"/base/files/chcp";
    parseCordovaConfigXml();
}

void HotCodePushPlugin::parseCordovaConfigXml()
{

    CordovaViewController* pCordovaViewController = (CordovaViewController*)Application::g_cordovaViewController;
    m_strChcpConfigUrl = pCordovaViewController->getChcpConfigUrl();
    m_isAllowUpdatesAutoDownload = pCordovaViewController->getAllAllowUpdatesAutoDownload();
    m_isAllowUpdatesAutoInstall = pCordovaViewController->getAllowUpdatesAutoInstall();
    m_nActiveInterfaceVersion = pCordovaViewController->getActiveInterfaceVersion(); 
}

void HotCodePushPlugin::fetchUpdate()
{
    //1处理本地文件chcp.json
    string strFilePath = Application::g_databaseDir.substr(0, Application::g_databaseDir.find("database")-1)+"/base/files/chcp/";
    string strChcpFile = "chcp.json";
    string strChcpJsonFilePath = strFilePath + strChcpFile;
    string strWww = "www/";
    if(!FileCache::IsFile(strChcpFile, strFilePath)) {
        string strTempChcpFile = strWww+strChcpFile;
        RawFile *rawfile = OH_ResourceManager_OpenRawFile(Application::g_resourceManager, strTempChcpFile.c_str());
        if(rawfile == NULL)
            return;

        long consumed = 0;
        unsigned  char* buffer = (unsigned char*)m_pBuf;
        while (true) {
            int ret = OH_ResourceManager_ReadRawFile(rawfile, buffer, const_max_buf);
            if (ret == 0) {
                break;
            }
            consumed += ret;
            OH_ResourceManager_SeekRawFile(rawfile, consumed, 0);
        }
        OH_ResourceManager_CloseRawFile(rawfile);
        buffer[consumed] = 0;
        if(!FileCache::writeFile(strChcpJsonFilePath, (const char*)buffer, consumed)) {
            return;
        }
    }
    
    //2处理本地文件chcp.mainfest
    string strChcpManifest = "chcp.manifest";
    string strChcpManifestFilePath = strFilePath + strChcpManifest;
    if(!FileCache::IsFile(strChcpManifest, strFilePath)) {
        string strTempChcpManifest = strWww+strChcpManifest;
        RawFile *rawfile = OH_ResourceManager_OpenRawFile(Application::g_resourceManager, strTempChcpManifest.c_str());
        if(rawfile == NULL)
            return;

        long consumed = 0;
        unsigned  char* buffer = (unsigned char*)m_pBuf;
        while (true) {
            int ret = OH_ResourceManager_ReadRawFile(rawfile, buffer, const_max_buf);
            if (ret == 0) {
                break;
            }
            consumed += ret;
            OH_ResourceManager_SeekRawFile(rawfile, consumed, 0);
        }
        OH_ResourceManager_CloseRawFile(rawfile);
        buffer[consumed] = 0;
        if(!FileCache::writeFile(strChcpManifestFilePath, (const char*)buffer, consumed)) {
            return;
        }
    }
    
    //3,加载本地chcp.json文件
    long consumed = 0;
    unsigned  char* buffer = (unsigned char*)m_pBuf;
    memset(buffer, 0, const_max_buf);
    if(!FileCache::readFile(strChcpJsonFilePath, buffer, const_max_buf)) {
        return;
    }
    if(m_pChcpFile != NULL) {
        cJSON_Delete(m_pChcpFile);
        m_pChcpFile = NULL;
    }
    m_pChcpFile = cJSON_Parse((const char*)buffer);
    
    //4,加载服务器端chcp.json文件
    string strCachePath = Application::g_databaseDir.substr(0, Application::g_databaseDir.find("database")-1)+"/base/cache/";
    string strCahceChcpJsonFilePath = strCachePath + strChcpFile;
    FileCache::deleteFile(strCahceChcpJsonFilePath);
    if(!downLoadFile(m_strChcpConfigUrl, strCahceChcpJsonFilePath)) {
        return;
    }
    
    consumed = 0;
    buffer = (unsigned char*)m_pBuf;
    memset(buffer, 0, const_max_buf);
    if(!FileCache::readFile(strCahceChcpJsonFilePath, buffer, const_max_buf)) {
        return;
    }
    if(m_pCacheChcpFile != NULL) {
        cJSON_Delete(m_pCacheChcpFile);
        m_pCacheChcpFile = NULL;
    }
    m_pCacheChcpFile = cJSON_Parse((const char*)buffer);
    
    //5比较版本是否有更新
    cJSON* pVersion = cJSON_GetObjectItem(m_pChcpFile, "release");
    if(pVersion == NULL) {
        return;
    }
    string strLocalVersion = pVersion->valuestring;
    cJSON* pCacheVersion = cJSON_GetObjectItem(m_pCacheChcpFile, "release");
    if(pCacheVersion == NULL) {
        return;
    }
    string strCacheVersion = pCacheVersion->valuestring;
    
    if(strLocalVersion == strCacheVersion) {
        return;
    }
    
    cJSON* pContentUrl = cJSON_GetObjectItem(m_pCacheChcpFile, "content_url");
    if(pContentUrl == NULL) {
        return;
    }
    string strRequest = pContentUrl->valuestring;
    
    //6加载本地chcp.mainfest文件
    consumed = 0;
    buffer = (unsigned char*)m_pBuf;
    memset(buffer, 0, const_max_buf);
    if(!FileCache::readFile(strChcpManifestFilePath, buffer, const_max_buf)) {
        return;
    }
    if(m_pChcpManifestFile != NULL) {
        cJSON_Delete(m_pChcpManifestFile);
        m_pChcpManifestFile = NULL;
    }
    m_pChcpManifestFile = cJSON_Parse((const char*)buffer);
    
    //7加载服务器chcp.mainfest文件
    string strChcpCacheManifestFilePath = strCachePath + strChcpManifest;
    FileCache::deleteFile(strChcpCacheManifestFilePath);
    if(!downLoadFile(strRequest+"/"+strChcpManifest, strChcpCacheManifestFilePath)) {
        return;
    }
    consumed = 0;
    buffer = (unsigned char*)m_pBuf;
    memset(buffer, 0, const_max_buf);
    if(!FileCache::readFile(strChcpCacheManifestFilePath, buffer, const_max_buf)) {
        return;
    }
    if(m_pChcpChacheManifestFile != NULL) {
        cJSON_Delete(m_pChcpChacheManifestFile);
        m_pChcpChacheManifestFile = NULL;
    }
    m_pChcpChacheManifestFile = cJSON_Parse((const char*)buffer);
    
    //8开始自动更新文件
    vector<string> vecCacheFileName;
    vector<string> vecCahceFileHash;
    int nCacheFileCount = cJSON_GetArraySize(m_pChcpChacheManifestFile);
    for(int i=0; i<nCacheFileCount; i++) {
        cJSON* pFileNode = cJSON_GetArrayItem(m_pChcpChacheManifestFile,i);
        cJSON* pFileName = cJSON_GetObjectItem(pFileNode, "file");
        string strCacheFileName = "";
        if(pFileName != NULL) {
            strCacheFileName = pFileName->valuestring;
        }
        cJSON* pFileHash = cJSON_GetObjectItem(pFileNode, "hash");
        string strCacheFileHash = "";
        if(pFileHash != NULL) {
            strCacheFileHash = pFileHash->valuestring;
        }
        vecCacheFileName.push_back(strCacheFileName);
        vecCahceFileHash.push_back(strCacheFileHash);
    }
    
    vector<string> vecLocalFileName;
    vector<string> vecLocalFileHash;
    int nLocalFileCount = cJSON_GetArraySize(m_pChcpManifestFile);
     for(int i=0; i<nLocalFileCount; i++) {
        cJSON* pFileNode = cJSON_GetArrayItem(m_pChcpManifestFile,i);
        cJSON* pFileName = cJSON_GetObjectItem(pFileNode, "file");
        string strLocalFileName = "";
        if(pFileName != NULL) {
            strLocalFileName = pFileName->valuestring;
        }
        cJSON* pFileHash = cJSON_GetObjectItem(pFileNode, "hash");
        string strLocalFileHash = "";
        if(pFileHash != NULL) {
            strLocalFileHash = pFileHash->valuestring;
        }
        vecLocalFileName.push_back(strLocalFileName);
        vecLocalFileHash.push_back(strLocalFileHash);
    }
    
    for(int i=0; i<vecCacheFileName.size(); i++) {
        int j=0;
        for(; j<vecLocalFileName.size(); j++) {
            if(vecCacheFileName[i] == vecLocalFileName[j]) {
                if(vecCahceFileHash[i] != vecLocalFileHash[j]) {
                    FileCache::deleteFile(strFilePath+strWww+vecCacheFileName[i]);
                    if(!downLoadFile(strRequest+"/"+vecCacheFileName[i], strFilePath+strWww+vecCacheFileName[i])) {
                        break;
                    }
                    m_nUpdateFlag = 1;
                }
                break;    
            }
        }
        
        if(j >= vecLocalFileName.size()) {
            FileCache::deleteFile(strFilePath+strWww+vecCacheFileName[i]);
            if(!downLoadFile(strRequest+"/"+vecCacheFileName[i], strFilePath+strWww+vecCacheFileName[i])) {
                continue;
            }
            m_nUpdateFlag = 1;
        }
    }
    
    //9将服务器文件拷贝到缓存
    FileCache::moveFile(strCahceChcpJsonFilePath, strChcpJsonFilePath);
    FileCache::moveFile(strChcpCacheManifestFilePath, strChcpManifestFilePath);
    return;
}

bool HotCodePushPlugin::downLoadFile(const string & strSource,const string& strTarget)
{
      //1，解析url
    string urlPath = strSource;
    string requestAddress = "";
    urlPath = urlPath.erase(0, urlPath.find_first_not_of(" "));
    urlPath = urlPath.erase(urlPath.find_last_not_of(" ") + 1);
    if(urlPath.find("//") != string::npos) {
        requestAddress = urlPath.substr(urlPath.find("//")+2);
    }
    
    //2,解析http/https协议类型
    bool isHttps = false;
    if(urlPath.find("https") == 0) {
        isHttps = true;
    } else if(urlPath.find("http") == 0) {
        isHttps = false;
    } else {
        return false;
    }
    
    //3解析域名修改请求地址
    string strDomain;
    if(urlPath.find("//") != string::npos) {
        strDomain = urlPath.substr(urlPath.find("//")+2);
        if(strDomain.find("/") != string::npos) {
            strDomain = strDomain.substr(0, strDomain.find("/"));
        }
    }
    requestAddress = requestAddress.substr(strDomain.length());
    
    string strRequest;
    strRequest = "GET " + requestAddress + " HTTP/1.1\r\n";//%s %s HTTP/1.1\r\n
    
    char szBuf[512];
    sprintf(szBuf, "Host:%s\r\n", strDomain.c_str());
    strRequest += szBuf;
    
    map<string, string>  requestHeaders = m_fetchUpdateOptions.getRequestHeaders();
    for(map<string,string>::iterator iter = requestHeaders.begin(); iter != requestHeaders.end(); ++iter){
        sprintf(szBuf, "%s:%s\r\n", iter->first.c_str(), iter->second.c_str());
        strRequest += szBuf;
    }
    
    sprintf(szBuf, "Connection:Keep-Alive\r\n");
    strRequest += szBuf;
    
    sprintf(szBuf, "Keep-Alive:timeout=5, max=1000\r\n\r\n");
    strRequest += szBuf;
    
    ConnPoolManage* connPoolManage = (ConnPoolManage*)Application::g_connPoolManage;
    ConnPool* pool = connPoolManage->getConnPool(strDomain);
    if(pool == NULL) {
        return false;
    }
    
    
    std::atomic<bool> isAbort{false};
    string strFileName = strTarget;
    map<string, string>  mapRespHeaders;
    for(int i=0; i<3; i++) {
        bool isBreak = true;
        if(isHttps) {
            bool isClose = false;
            SSL* ssl = NULL;
            if(!pool->getSSL(&ssl)) {
                OH_LOG_ERROR(LOG_APP, "pool->getSocket(&ssl) == false");
                isBreak = false;
            }else if(!pool->sendWithTimeOut(ssl, strRequest, std::ref(isAbort))) {
                OH_LOG_ERROR(LOG_APP, "pool->sendWithTimeOut(ssl, strRequest) == false");
                isBreak = false;
            }else if(!pool->recvHttpWithTimeOut(ssl, strFileName, mapRespHeaders, "", m_pBuf, const_max_buf, isClose, std::ref(isAbort), NULL)) {
                OH_LOG_ERROR(LOG_APP, "pool->recvHttpWithTimeOut(ssl, strFileName, pMaxBuffer, nBufSize, isReconnect) == false");
                isBreak = false;
            }
            if(ssl != NULL) {
                pool->freeSocket(ssl, isClose);
            }
        } else {
            bool isClose = false;
            int nSocket = -1;
            if(!pool->getSocket(nSocket)) {
                OH_LOG_ERROR(LOG_APP, "pool->getSocket(nSocket) == false");
                isBreak = false;
            }else if(!pool->sendWithTimeOut(nSocket, strRequest, std::ref(isAbort))) {
                OH_LOG_ERROR(LOG_APP, "pool->sendWithTimeOut(nSocket, strRequest) == false");
                isBreak = false;
            }else if(!pool->recvHttpWithTimeOut(nSocket, strFileName, mapRespHeaders, "", m_pBuf, const_max_buf, isClose, std::ref(isAbort), NULL)) {
                OH_LOG_ERROR(LOG_APP, "pool->recvHttpWithTimeOut(nSocket, strFileName, pMaxBuffer, nBufSize, isReconnect) == false");
                isBreak = false;
            }
            if(nSocket != -1) {
                pool->freeSocket(nSocket, true);
            }
        }
        if(isBreak)    
            break;    
    }
    
    if(!FileCache::IsFile(strFileName, "")) {
        return false;
    }
    return true;
}

void HotCodePushPlugin::sendPluginMessage()
{
    if(m_nUpdateFlag == 0) {
        cJSON* json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "action", "chcp_nothingToUpdate");
        cJSON_AddNumberToObject(json, "error", 2);
        cJSON_AddNullToObject(json, "data");
        m_cbc.success(json);
    }
   
    if(m_nUpdateFlag == 1) {
        cJSON* json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "action", "chcp_updateIsReadyToInstall");
        cJSON_AddNullToObject(json, "error");
        cJSON_AddNullToObject(json, "data");
        m_cbc.success(json);
    }   
}

void HotCodePushPlugin::CheckUpdatePlugin::Execute(void* arg) 
{
    HotCodePushPlugin* pHotCodePushPlugin = (HotCodePushPlugin*)arg;
    pHotCodePushPlugin->fetchUpdate();
    pHotCodePushPlugin->sendPluginMessage();
}